/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*******************************************************************************/
package org.apache.wink.example.locking.resources;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Date;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import org.apache.wink.common.annotations.Workspace;
import org.apache.wink.example.locking.legacy.DefectBean;
import org.apache.wink.example.locking.legacy.DefectsBean;
import org.apache.wink.example.locking.store.DataStore;
/**
* <p>
* This example demonstrates usage of Preconditions to create an <a
* href="http://en.wikipedia.org/wiki/Optimistic_concurrency_control">Optimistic
* Concurrency Control</a> functionality.
*/
@Path("defects")
@Workspace(workspaceTitle = "QA Defects", collectionTitle = "Defects")
public class DefectResource {
public static final String DEFECT = "defect";
public static final String DEFECT_URL = "/{" + DEFECT + "}";
/**
* memory store
*/
private DataStore store = DataStore.getInstance();
/**
* Returns the collection of defects.
* <p>
* If the store wasn't modified from the last call, the method returns 304
* (NOT_MODIFIED).
*/
@GET
@Produces(MediaType.APPLICATION_XML)
public Response getDefectsCollection(@Context Request request) {
// verify that the store was modified since the last call
Date lastModifiedIgnoreMillis = store.getLastModifiedIgnoreMillis();
ResponseBuilder precondition = request.evaluatePreconditions(lastModifiedIgnoreMillis);
if (precondition != null) {
// the collection wasn't modified, 304 will be returned
return precondition.build();
}
// the collection was modified, retrieve it from the store
Collection<DefectBean> defects = store.getDefects();
// return the collection and add its last modified date on the response
return Response.ok(new DefectsBean(defects)).lastModified(lastModifiedIgnoreMillis).build();
}
/**
* Returns a single defect.
* <p>
* If the defect with the given id doesn't exist in the store, 404 NOT_FOUND
* is returned
* <p>
* If IF_NONE_MATCH header present, the defect will be returned only if it
* was modified since the previous call.
*/
@GET
@Path(DEFECT_URL)
@Produces(MediaType.APPLICATION_XML)
public Response getDefect(@Context Request request, @PathParam(DEFECT) String defectId) {
// get defect from the store
DefectBean bean = store.getDefect(defectId);
if (bean == null) {
// defect was not found
return Response.status(Status.NOT_FOUND).build();
}
// create defect's etag
EntityTag defectBeanEtag = new EntityTag(String.valueOf(bean.hashCode()));
// evaluate the precondition
ResponseBuilder precondition = request.evaluatePreconditions(defectBeanEtag);
if (precondition != null) {
// defect was not modified, return 304
return precondition.build();
}
// create response the defect and its entity tag
return Response.ok(bean).tag(defectBeanEtag).build();
}
/**
* Adds a new defect to the collection. The created defect is returned along
* with its etag to the client.
*/
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response addDefect(@Context UriInfo uriInfo, DefectBean bean) throws IOException,
URISyntaxException {
// verify that bean was sent
if (bean == null) {
throw new WebApplicationException(Status.BAD_REQUEST);
}
// set unique Id in the new defect bean:
// - Id in the input data is ignored, actually there should be no Id
// there,
bean.setId(store.getDefectUniqueId());
// add defect bean to the memory store
store.putDefect(bean.getId(), bean);
// header Location (absolute URI) must exist on the response in case of
// status code 201
URI location = new URI(uriInfo.getAbsolutePath() + "/" + bean.getId());
// create entity tag, so the Client can use it for OCC
EntityTag entityTag = new EntityTag(String.valueOf(bean.hashCode()));
return Response.status(Status.CREATED).entity(bean).location(location).tag(entityTag)
.build();
}
/**
* Updates defect.
* <p>
* The defect is updated only if the If-Match header is present and
* evaluation of precondition succeeds. This is done to ensure the OCC.
*/
@PUT
@Path(DEFECT_URL)
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response updateDefect(@Context Request request,
@PathParam(DEFECT) String defectId,
@HeaderParam(HttpHeaders.IF_MATCH) String ifMatchHeader,
DefectBean updatedBean) throws IOException {
if (ifMatchHeader == null) {
// IF-MATCH header wasn't sent, cannot validate the precondition
throw new WebApplicationException(Status.BAD_REQUEST);
}
// obtain data object from the memory store
DefectBean bean = store.getDefect(defectId);
if (bean == null) {
// not found, return 404
throw new WebApplicationException(Status.NOT_FOUND);
}
// create defect's etag
EntityTag defectBeanEtag = new EntityTag(String.valueOf(bean.hashCode()));
ResponseBuilder preconditions = request.evaluatePreconditions(defectBeanEtag);
if (preconditions != null) {
return preconditions.build();
}
updatedBean.setId(defectId);
// update defect legacy bean to the memory store
store.putDefect(defectId, updatedBean);
Response response =
Response.ok(updatedBean).tag(new EntityTag(String.valueOf(updatedBean.hashCode())))
.build();
return response;
}
/**
* Deletes defect.
* <p>
* The defect is deleted only if If-Match is present and valid to ensure
* OCC.
*/
@DELETE
@Path(DEFECT_URL)
@Produces(MediaType.APPLICATION_XML)
public Object deleteDocument(@Context Request request,
@PathParam(DEFECT) String defectId,
@HeaderParam(HttpHeaders.IF_MATCH) String ifMatchHeader) {
if (ifMatchHeader == null) {
// IF-MATCH header wasn't sent, cannot validate the precondition
throw new WebApplicationException(Status.BAD_REQUEST);
}
// obtain data object from memory store
DefectBean bean = store.getDefect(defectId);
if (bean == null) {
// defect not found, return 404
throw new WebApplicationException(Status.NOT_FOUND);
}
// create defect's etag
EntityTag defectBeanEtag = new EntityTag(String.valueOf(bean.hashCode()), false);
ResponseBuilder preconditions = request.evaluatePreconditions(defectBeanEtag);
if (preconditions != null) {
return preconditions.build();
}
return store.removeDefect(defectId);
}
}